package com.lagou.edu.spring.mybatis.transaction;

import com.lagou.edu.spring.core.BeanPostProcessor;
import com.lagou.niancheng.core.session.SqlSessionFactory;
import com.lagou.niancheng.core.transaction.TransactionManager;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

/**
 * com.lagou.edu.spring.mybatis.transaction.MyTransactionalParseProcess
 *
 * @author 大犬
 * @date 2020/4/3
 */
public class MyTransactionalParseProcess implements BeanPostProcessor {

    private SqlSessionFactory sqlSessionFactory;

    public MyTransactionalParseProcess(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public Object afterProcess(String beanName, Object bean) {
        Class<?> aClass = bean.getClass();
        Method[] declaredMethods = aClass.getDeclaredMethods();
        boolean hasTransactionalMethod = false;
        for (Method declaredMethod : declaredMethods) {
            MyTransactional myTransactional = declaredMethod.getAnnotation(MyTransactional.class);
            if (myTransactional != null) {
                hasTransactionalMethod = true;
            }
        }
        // 如果该bean没有事务方法，则不需要处理
        if (!hasTransactionalMethod) {
            return bean;
        }
        // 否则根据类型判断类是否有接口
        // 如果有接口，则使用jdk动态代理生成
        // 如果没有，则使用cglib动态代理生成
        Class<?>[] interfaces = aClass.getInterfaces();
        TransactionManager transactionManager = sqlSessionFactory.getConfiguration().getTransactionManager();
        if (interfaces != null && interfaces.length != 0) {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, new TransactionMethod(bean, transactionManager));
        } else {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(aClass);
            enhancer.setCallback(new TransactionMethod(bean, transactionManager));
            return enhancer.create();
        }
    }


    private static class TransactionMethod implements InvocationHandler, MethodInterceptor {

        private TransactionManager transactionManager;
        private Object originBean;

        public TransactionMethod(Object originBean, TransactionManager transactionManager) {
            this.originBean = originBean;
            this.transactionManager = transactionManager;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return invokeTransactionMethod(method, () -> method.invoke(originBean, args));
        }

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

            return invokeTransactionMethod(method, () -> methodProxy.invokeSuper(o, objects));
        }

        private Object invokeTransactionMethod(Method method, Invoker supplier) throws Throwable {
            Method originMethod = originBean.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
            if (originMethod.getAnnotation(MyTransactional.class) == null) {
                Connection connection = transactionManager.openTransaction();
                connection.setAutoCommit(true);
                return supplier.get();
            }
            // 1.开启事务
            Connection connection = transactionManager.openTransaction();
            if (connection.getAutoCommit()) {
                connection.setAutoCommit(false);
            }
            // 2.执行方法
            try {
                Object result = supplier.get();
                // 3.事务提交
                connection.commit();
                return result;
            } catch (Throwable e) {
                // 4.如果发生了异常，事务回滚
                connection.rollback();
                if (e.getClass() == InvocationTargetException.class) {
                    throw e.getCause();
                }
                throw e;
            }
        }

        interface Invoker {
            Object get() throws Throwable;
        }
    }
}
